﻿using System;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using System.Collections.Generic;

namespace Demo
{
    class Effect : GameWindow
    {
        const int maxripplecount = 256;
        public Effect() : base(960, 720, new GraphicsMode(), "OpenTK Dope", GameWindowFlags.FixedWindow)
        {
            MakeCurrent();
            this.VSync = VSyncMode.Off;

            surface = InitSurface();
            viewpanel = InitViewPanel();

            spRipple = new ShaderProgram();
            spEffect = new ShaderProgram();
            spRipple.InitShader(File.ReadAllText("ripple.glvs"), File.ReadAllText("ripple.glfs"));
            spEffect.InitShader(File.ReadAllText("effect.glvs"), File.ReadAllText("effect.glfs"));
            texBackground = GLLoadTextureFromFile("./background.jpg");
            texCaustics = CreateTexImageCaustics(W,H);


            //rpRipple = new RenderPass();
            rpRipple = RenderPass.CreateFrameBufferTexture(Width, Height);//frame buffer size equal to window
            rpEffect = new RenderPass();
            rpRipple.RenderFunc = rpRippleAction;
            rpEffect.RenderFunc = rpEffectAction;
        }

        VBO surface;
        VBO viewpanel;
        ShaderProgram spRipple;
        ShaderProgram spEffect;
        RenderPass rpRipple;
        RenderPass rpEffect;

        float[] pts = new float[maxripplecount * 3];//波纹中心点

        //int W = 320, H = 180;//16:9
        const int W = 240, H = 180;//4:3
        
        //改变窗口时，更新视口
        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);
            GL.Viewport(0, 0, Width, Height);
        }

        private int offset;
        //鼠标操作，按下
        protected override void OnMouseDown(MouseButtonEventArgs e)
        {
            base.OnMouseDown(e); 
            offset += 1;
            if (offset >= maxripplecount)
                offset = 0;
            pts[offset * 3 + 0] = ((float)e.Position.X / Width - 0.5f) * W;
            pts[offset * 3 + 1] = (1.0f - (float)e.Position.Y / Height - 0.5f) * H;
            pts[offset*3 + 2] = 0.001f;
            
        }
        //鼠标操作，移动
        protected override void OnMouseMove(MouseMoveEventArgs e)
        {
            base.OnMouseMove(e);
            if (e.Mouse.LeftButton==OpenTK.Input.ButtonState.Pressed)
            {
                offset += 1;
                if (offset >= maxripplecount)
                    offset = 0;
                pts[offset * 3 + 0] = ((float)e.Position.X / Width - 0.5f) * W;
                pts[offset*3 + 1] = (1.0f - (float)e.Position.Y / Height - 0.5f) * H;
                pts[offset*3 + 2] = 0.001f;
            }
        }

        int texBackground;
        int texCaustics;

        public int GLLoadTextureFromFile(string filepath)
        {
            if (!File.Exists(filepath))
                return 0;
            int tex = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, tex);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);

            Bitmap img = (Bitmap)Image.FromFile(filepath);
            BitmapData imgdata = img.LockBits(new System.Drawing.Rectangle(0, 0, img.Width, img.Height),
                ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, img.Width, img.Height, 0, OpenTK.Graphics.OpenGL.PixelFormat.Bgra,
                PixelType.UnsignedByte, imgdata.Scan0);
            img.UnlockBits(imgdata);
            return tex;
        }

        //创建可以存储caustics的纹理
        public int CreateTexImageCaustics(int w, int h)
        {
            int tex = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, tex);
            GL.TexStorage2D(TextureTarget2d.Texture2D, 1, SizedInternalFormat.R32i,w,h);
            GL.BindTexture(TextureTarget.Texture2D, 0);
            return tex;
        }

        private VBO InitSurface()
        {
            int W1 = W + 1, H1 = H + 1;
            float[] vtx_surface = new float[W1 * H1 * 3];
            uint[] idx_surface = new uint[W * H * 6];
            for (int y = 0; y <= H; y++)
            {
                for (int x = 0; x <= W; x++)
                {
                    vtx_surface[(x + y * W1) * 3 + 0] = (1.0f * x / W - 0.5f) * W;
                    vtx_surface[(x + y * W1) * 3 + 1] = (1.0f * y / H - 0.5f) * H;
                    vtx_surface[(x + y * W1) * 3 + 2] = 0;
                }
            }

            for (int y = 0; y < H; y++)
            {
                for (int x = 0; x < W; x++)
                {
                    idx_surface[(x + y * W) * 6 + 0] = (uint)(x + y * W1);
                    idx_surface[(x + y * W) * 6 + 1] = (uint)(x + 1 + y * W1);
                    idx_surface[(x + y * W) * 6 + 2] = (uint)(x + (y + 1) * W1);

                    idx_surface[(x + y * W) * 6 + 3] = (uint)(x + (1 + y) * W1);
                    idx_surface[(x + y * W) * 6 + 4] = (uint)(x + 1 + y * W1);
                    idx_surface[(x + y * W) * 6 + 5] = (uint)(x + 1 + (y + 1) * W1);
                }
            }
            return new VBO(vtx_surface, idx_surface);
        }

        private VBO InitViewPanel()
        {
            float[] vtx_surface = {
                -1,1,0,
                1,1,0,
                1,-1,0,
                -1,-1,0
            };
            uint[] idx_surface = {
                0,1,2,
                2,3,0
            };
            return new VBO(vtx_surface, idx_surface);
        }

        private void UpdateTransform_Ripple()
        {
            Matrix4 Model = Matrix4.Identity;
            //Matrix4 View = Matrix4.LookAt(new Vector3(0, 0, H / 2.0f), new Vector3(), Vector3.UnitY);
            //Matrix4 Projection = Matrix4.CreatePerspectiveFieldOfView(3.1415926f / 2, 1.0f * Width / Height, 0.1f, 1000);

            Matrix4 View = Matrix4.Identity;
            Matrix4 Projection = Matrix4.CreateOrthographic(W, H, -1000, 1000);
            spRipple.SetupUniforms(Model, View, Projection);
        }

        private void UpdateTransform_Effect()
        {
            spEffect.SetupUniforms(Matrix4.Identity, Matrix4.Identity, Matrix4.Identity);
        }

        DateTime last = DateTime.Now;
        int frames = 0;
        protected override void OnRenderFrame(FrameEventArgs e)
        {
            #region "FPS"
            DateTime cur = DateTime.Now;
            double dt = (cur - last).TotalMilliseconds;
            frames += 1;
            if (dt >= 1000)
            {
                this.Title = "FPS:" + (frames / dt * 1000.0f).ToString("F2");
                frames = 0;
                last = cur;
            }
            #endregion

            #region "Ripple Motion"
            for (int i = 0; i < maxripplecount; i++)
            {
                if (pts[i*3+2] > 5)//大于5秒
                {
                    pts[i*3+2] = 0;
                    pts[i*3+0] = 0;
                    pts[i*3+1] = 0;
                }
                if (pts[i*3+2] > 0)//有效点
                    pts[i*3+2] = pts[i*3+2] + (float)e.Time;
            }
            #endregion
            
            rpRipple.Render();
            rpEffect.Render();
            this.SwapBuffers();
        }

        void rpRippleAction()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.ClearColor(0.0f, 0.0f, 0.0f, 1.0f);
            //GL.PolygonMode(MaterialFace.FrontAndBack, PolygonMode.Line);

            //GL.BindTexture(TextureTarget.Texture2D, texdata);
            //GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgb32f, 16, 16, 0, OpenTK.Graphics.OpenGL.PixelFormat.Rgb, PixelType.Float, pts);
            //RGB32f是允许float不在[0,1]范围内的
            GL.UseProgram(spRipple.shaderProgramHandle);
            UpdateTransform_Ripple();
            int uptsArr = GL.GetUniformLocation(spRipple.shaderProgramHandle, "pts");
            GL.Uniform3(uptsArr, maxripplecount * 3, pts);
            int uwidth = GL.GetUniformLocation(spRipple.shaderProgramHandle, "width");
            int uheight = GL.GetUniformLocation(spRipple.shaderProgramHandle, "height");
            GL.Uniform1(uwidth, (float)Width);//use window size as uniform size.
            GL.Uniform1(uheight, (float)Height);
            int[] zero = {0};
            GL.ClearTexImage(texCaustics, 0, OpenTK.Graphics.OpenGL.PixelFormat.RedInteger, PixelType.Int, zero);
            GL.BindImageTexture(0, texCaustics, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.R32i);

            //GL.Enable(EnableCap.Texture2D);
            //GL.BindTexture(TextureTarget.Texture2D, texbackground);
            //int utexbackground = GL.GetUniformLocation(spRipple.shaderProgramHandle, "texbackground");
            //GL.Uniform1(utexbackground, 0);
            surface.Draw();
            GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
            GL.Flush();
        }

        void rpEffectAction()
        {
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.ClearColor(0.5f, 0.5f, 0.5f, 1.0f);

            GL.UseProgram(spEffect.shaderProgramHandle);
            UpdateTransform_Effect();
            GL.Enable(EnableCap.Texture2D);

            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindTexture(TextureTarget.Texture2D, texBackground);
            int utexbackground = GL.GetUniformLocation(spEffect.shaderProgramHandle, "texbackground");
            GL.Uniform1(utexbackground, 0);

            GL.ActiveTexture(TextureUnit.Texture0 + 1);
            GL.BindTexture(TextureTarget.Texture2D, rpRipple.RenderTexture);
            int utexripple = GL.GetUniformLocation(spEffect.shaderProgramHandle, "texripple");
            GL.Uniform1(utexripple, 1);

            GL.ActiveTexture(TextureUnit.Texture0 + 2);
            GL.BindTexture(TextureTarget.Texture2D, texCaustics);
            int utexcaustics = GL.GetUniformLocation(spEffect.shaderProgramHandle, "texcaustics");
            GL.Uniform1(utexcaustics, 2);

            int uwidth = GL.GetUniformLocation(spEffect.shaderProgramHandle, "width");
            int uheight = GL.GetUniformLocation(spEffect.shaderProgramHandle, "height");
            GL.Uniform1(uwidth, (float)Width);//use window size as uniform size.
            GL.Uniform1(uheight, (float)Height);

            viewpanel.Draw();
            GL.Flush();
        }
        
        static void Main(string[] args)
        {
            using (Effect dope = new Effect())
            {
                dope.Run();
            }
        }
    }
}

namespace TK
{
    public static class Arr
    {
        public static Vector3 ToVector3(int[] a)
        {
            if (a.Length != 3) 
                throw new Exception("Data size not match");
            return new Vector3(a[0],a[1],a[2]);
        }

        public static Vector4 ToVector4(int[] a)
        {
            if (a.Length != 4)
                throw new Exception("Data size not match");
            return new Vector4(a[0], a[1], a[2], a[3]);
        }

        public static Matrix4 ToMatrix4(float[] a)
        {
            if (a.Length != 16)
                throw new Exception("Data size not match");
            return new Matrix4(a[0], a[1], a[2],a[3],a[4],a[5],a[6],a[7],
                a[8],a[9],a[10],a[11],a[12],a[13],a[14],a[15]);
        }
    }

    public static class Glu
    {
        //来自GLU源码
        public static void dopeMatrix(int x, int y, int deltax, int deltay, ref int[] viewer)
        {
            if (deltax < 0 || deltay < 0)
            {
                return;
            }
            else
            {
                //平移缩放拾取区域，让区域变成整个窗口大小
                GL.Translate((viewer[2] - 2 * (x - viewer[0])) / deltax, (viewer[3] - 2 * (y - viewer[1])) / deltay, 0);
                GL.Scale(viewer[2] / deltax, viewer[3] / deltay, 1.0);
            }
        }

        //来自GLU源码，OpenGL默认屏幕坐标坐标到空间坐标,好像只是顶点变成[-1,1]，视口是[0,1]
        public static bool UnProject(Vector3 win, Matrix4 modelview, Matrix4 projection, Vector4 viewport, ref Vector3 obj)
        {
            Matrix4 finalMat;
            Vector4 inVec;
            Vector4 outVec;
            finalMat = Matrix4.Mult(modelview, projection);
            finalMat = Matrix4.Invert(finalMat);
            inVec = new Vector4(win, 1.0f);

            //映射窗口的x,y坐标 0到1
            inVec[0] = (inVec[0] - viewport[0]) / viewport[2];
            inVec[1] = (inVec[1] - viewport[1]) / viewport[3];
            //映射范围变成 -1 到 1
            inVec[0] = inVec[0] * 2 - 1;
            inVec[1] = inVec[1] * 2 - 1;
            inVec[2] = inVec[2] * 2 - 1;

            outVec = MultMatrixVec(finalMat, inVec);
            if (outVec[3] == 0.0) return false;
            outVec[0] /= outVec[3];
            outVec[1] /= outVec[3];
            outVec[2] /= outVec[3];
            obj.X = outVec[0];
            obj.Y = outVec[1];
            obj.Z = outVec[2];
            return true;
        }

        //OpenTK不带4x4矩阵跟Vec4相乘，去
        public static Vector4 MultMatrixVec(Matrix4 matrix, Vector4 inVec)
        {
            int i;
            Vector4 result = new Vector4();
            for (i = 0; i < 4; i++)
            {
                result[i] =
                inVec[0] * matrix[0, i] +
                inVec[1] * matrix[1, i] +
                inVec[2] * matrix[2, i] +
                inVec[3] * matrix[3, i];
            }
            return result;
        }
    }
}

